package br.pucrs.lognplay.service;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.ejb.Stateless;

import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.joda.time.format.DateTimeFormat;
import org.elasticsearch.index.query.AndFilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.FilteredQueryBuilder;
import org.elasticsearch.index.query.OrFilterBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortBuilders;

import br.pucrs.lognplay.entity.LogSource;
import br.pucrs.lognplay.entity.Tag;
import br.pucrs.lognplay.gateway.SearchGateway;
import br.pucrs.lognplay.vo.Log;

@Stateless
public class SearchServiceBean implements SearchService {

	private static final String TO_ES_DATETIME_FORMAT = "YYYY-MM-dd'T'HH:mm:ss.SSS";

	private static final String FROM_ES_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS";

	public List<Log> listLogByCriteria(String viewId, List<Tag> tags, Date from, Date to, String text, List<String> severities) {
		SearchGateway.getInstance().openClient();
		Client client = SearchGateway.getInstance().getClient();
		SearchRequestBuilder searchRequestBuilder = client.prepareSearch(formatId(viewId));

		QueryBuilder queryBuilder = null;

		// Text
		if (text != null && !text.isEmpty()) {
			queryBuilder = QueryBuilders.queryString(text);

			QueryStringQueryBuilder queryStringQueryBuilder = (QueryStringQueryBuilder) queryBuilder;
			queryStringQueryBuilder.field("msg");
			queryStringQueryBuilder.field("syslogSeverityText");
			queryStringQueryBuilder.field("hostName");
			queryStringQueryBuilder.field("syslogFacilityText");
		} else {
			queryBuilder = QueryBuilders.matchAllQuery();
		}

		AndFilterBuilder andFilter = new AndFilterBuilder();

		// From and To
		if (from != null && to != null) {
			// From
			Calendar calendarFrom = Calendar.getInstance();
			calendarFrom.setTime(from);

			// To
			Calendar calendarTo = Calendar.getInstance();
			calendarTo.setTime(to);

			// @formatter:off
			andFilter
				.add(FilterBuilders
					.rangeFilter("timeReported")
					.gt(formatDate(calendarFrom, TO_ES_DATETIME_FORMAT))
					.lt(formatDate(calendarTo, TO_ES_DATETIME_FORMAT)));
			// @formatter:on
		}

		// Tags and Severities
		if (!tags.isEmpty() || !severities.isEmpty()) {
			OrFilterBuilder orFilterBuilder = new OrFilterBuilder();

			// Tags
			Set<LogSource> logSources = new HashSet<>();

			for (Tag tag : tags) {
				logSources.addAll(tag.getLogSources());
			}

			List<String> logSourceIds = new ArrayList<>();

			for (LogSource logSource : logSources) {
				logSourceIds.add(formatId(logSource.getId()));
			}

			for (String logSourceId : logSourceIds) {
				orFilterBuilder.add(FilterBuilders.termFilter("appName", logSourceId));
			}

			// Severities
			for (String severity : severities) {
				orFilterBuilder.add(FilterBuilders.termFilter("syslogSeverity", severity));
			}

			andFilter.add(orFilterBuilder);
		}

		FilteredQueryBuilder filteredQuery = new FilteredQueryBuilder(queryBuilder, andFilter);

		searchRequestBuilder.setSearchType(SearchType.QUERY_AND_FETCH);
		searchRequestBuilder.setQuery(filteredQuery);

		searchRequestBuilder.addHighlightedField("msg", 100, 1);
		searchRequestBuilder.setHighlighterPreTags("<b>");
		searchRequestBuilder.setHighlighterPostTags("</b>");

		searchRequestBuilder.addField("timeReported");
		searchRequestBuilder.addField("msg");
		searchRequestBuilder.addField("syslogSeverityText");
		searchRequestBuilder.addField("hostName");
		searchRequestBuilder.addField("syslogFacilityText");

		searchRequestBuilder.addSort(SortBuilders.scoreSort());

		SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();

		List<Log> logs = new ArrayList<>();

		if (searchResponse.getHits().totalHits() > 0) {
			for (SearchHit searchHit : searchResponse.getHits()) {
				Log log = new Log();

				try {
					String timestampHit = searchHit.field("timeReported").getValue().toString().trim();

					SimpleDateFormat formatter = new SimpleDateFormat(FROM_ES_DATETIME_FORMAT);
					Date timestamp;

					timestamp = formatter.parse(timestampHit);
					log.setTimestamp(timestamp);
				} catch (ParseException e) {
					e.printStackTrace();
				}

				log.setMessage(searchHit.field("msg").getValue().toString().trim());
				log.setSeverity(searchHit.field("syslogSeverityText").getValue().toString().trim());
				log.setHostname(searchHit.field("hostName").getValue().toString().trim());
				log.setFacility(searchHit.field("syslogFacilityText").getValue().toString().trim());
				logs.add(log);
			}
		}

		SearchGateway.getInstance().closeClient();

		return logs;
	}

	private String formatId(String id) {
		return id.replace("-", "").toLowerCase();
	}

	private String formatDate(Calendar date, String pattern) {
		return DateTimeFormat.forPattern(pattern).print(new DateTime(date));
	}
}
